/**
 * @author Javier Guaje <jrguajeg@gmail.com>
 */

package co.edu.unal.satacredito.database.dao;

import co.edu.unal.satacredito.database.constants.TransactionConstants;
import co.edu.unal.satacredito.database.entity.Transaction;
import co.edu.unal.satacredito.database.exception.NonexistentEntityException;
import co.edu.unal.satacredito.database.exception.PreexistingEntityException;
import co.edu.unal.satacredito.database.exception.UnreachableHostException;
import com.google.code.morphia.Datastore;
import com.google.code.morphia.Key;
import com.google.code.morphia.mapping.Mapper;
import com.google.code.morphia.query.Query;
import com.google.code.morphia.query.UpdateOperations;
import com.google.code.morphia.query.UpdateResults;
import com.mongodb.WriteResult;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bson.types.ObjectId;

public class TransactionMongoDao {
    
    private static final String TRANSACTION_ID = "_id";
    private static final String FILE_ID = "fileId";
    private static final String FILE_NUMBER = "fileNumber";
    private static final String NAME = "name";
    private static final String NIN = "nin";
    private static final String NIN_TYPE = "ninType";
    private static final String CARD_NUMBER = "cardNumber";
    private static final String CARD_CODE = "cardCode";
    private static final String CARD_TYPE = "cardType";
    private static final String CARD_EXPIRATION_DATE = "cardExpirationDate";
    private static final String BANK = "bank";
    private static final String FRANCHISE = "franchise";
    private static final String PURCHASE_DATE = "purchaseDate";
    private static final String PURCHASE_AMOUNT = "purchaseAmount";
    private static final String PURCHASE_DETAIL = "purchaseDetail";
    private static final String VALIDATION_DATE = "validationDate";
    private static final String VALIDATION_RESULT = "validationResult";
    private static final String VALIDATION_ISSUE = "validationIssue";
    private static final String READ_STATE = "readState";
    
    private Datastore mDs;

    public TransactionMongoDao(Datastore datastore) {
        this.mDs = datastore;
    }
    
    public ObjectId create(Transaction newTransaction) throws UnreachableHostException {
        try {
            Query<Transaction> query = mDs.find(Transaction.class);
            query.field(NIN).equal(newTransaction.getNin());
            query.field(NIN_TYPE).equal(newTransaction.getNinType());
            query.field(CARD_NUMBER).equal(newTransaction.getCardNumber());
            query.field(CARD_CODE).equal(newTransaction.getCardCode());
            query.field(CARD_TYPE).equal(newTransaction.getCardType());
            query.field(PURCHASE_DATE).equal(newTransaction.getPurchaseDate());
            Transaction transaction = query.get();
            if (transaction != null) {
                throw new PreexistingEntityException(PreexistingEntityException.ERROR_MESSAGE);
            }
            Key<Transaction> key = mDs.save(newTransaction);
            if (key == null) {
                throw new UnreachableHostException(UnreachableHostException.ERROR_MESSAGE);
            }
            return (ObjectId) key.getId();
        } catch (PreexistingEntityException pee) {
            Logger.getLogger(TransactionMongoDao.class.getName()).log(Level.SEVERE, null, pee);
            return null;
        }
    }
    
    public boolean delete(ObjectId id) throws UnreachableHostException {
        try {
            Query<Transaction> query = mDs.find(Transaction.class).field(Mapper.ID_KEY).equal(id);
            Transaction transaction = query.get();
            if (transaction == null) {
                throw new NonexistentEntityException(NonexistentEntityException.ERROR_MESSAGE);
            }
            WriteResult result = mDs.delete(Transaction.class, id);
            if (result == null) {
                throw new UnreachableHostException(UnreachableHostException.ERROR_MESSAGE);
            }
            return true;
        } catch (NonexistentEntityException nee) {
            Logger.getLogger(TransactionMongoDao.class.getName()).log(Level.SEVERE, null, nee);
            return false;
        }
    }
    
    public boolean update(ObjectId id, Transaction newTransaction) throws UnreachableHostException {
        try {
            Query<Transaction> query = mDs.createQuery(Transaction.class).field(Mapper.ID_KEY).equal(id);
            Transaction transaction = query.get();
            if (transaction == null) {
                throw new NonexistentEntityException(NonexistentEntityException.ERROR_MESSAGE);
            }
            UpdateOperations<Transaction> update = mDs.createUpdateOperations(Transaction.class);
            update.set(FILE_ID, newTransaction.getFileId());
            update.set(FILE_NUMBER, newTransaction.getFileNumber());
            update.set(NAME, newTransaction.getName());
            update.set(NIN, newTransaction.getNin());
            update.set(NIN_TYPE, newTransaction.getNinType());
            update.set(CARD_NUMBER, newTransaction.getCardNumber());
            update.set(CARD_CODE, newTransaction.getCardCode());
            update.set(CARD_TYPE, newTransaction.getCardType());
            update.set(CARD_EXPIRATION_DATE, newTransaction.getCardExpirationDate());
            update.set(BANK, newTransaction.getBank());
            update.set(FRANCHISE, newTransaction.getFranchise());
            update.set(PURCHASE_DATE, newTransaction.getPurchaseDate());
            update.set(PURCHASE_AMOUNT, newTransaction.getPurchaseAmount());
            update.set(PURCHASE_DETAIL, newTransaction.getPurchaseDetail());
            update.set(VALIDATION_DATE, newTransaction.getValidationDate());
            update.set(VALIDATION_RESULT, newTransaction.getValidationResult());
            update.set(VALIDATION_ISSUE, newTransaction.getValidationIssue());
            update.set(READ_STATE, newTransaction.getReadState());
            UpdateResults<Transaction> result = mDs.update(query, update);
            if (result == null) {
                throw new UnreachableHostException(UnreachableHostException.ERROR_MESSAGE);
            }
            return true;
        } catch (NonexistentEntityException nee) {
            Logger.getLogger(TransactionMongoDao.class.getName()).log(Level.SEVERE, null, nee);
            return false;
        }
    }
    
    public Transaction findById(ObjectId id) throws UnreachableHostException {
        return mDs.find(Transaction.class).field(Mapper.ID_KEY).equal(id).get();
    }
    
    public List<Transaction> findFailedByDateRangeAndIssueNin(Date initDate, Date endDate) {
        Query<Transaction> query = mDs.find(Transaction.class);
        query.field(VALIDATION_RESULT).equal(TransactionConstants.RESULT_FAILED);
        query.field(VALIDATION_ISSUE).equal(TransactionConstants.ISSUE_NIN);
        query.field(PURCHASE_DATE).greaterThanOrEq(initDate);
        query.field(PURCHASE_DATE).lessThanOrEq(endDate);
        return query.asList();
    }
    
    public List<Transaction> findFailedByDateRangeAndIssueCard(Date initDate, Date endDate) {
        Query<Transaction> query = mDs.find(Transaction.class);
        query.field(VALIDATION_RESULT).equal(TransactionConstants.RESULT_FAILED);
        query.field(VALIDATION_ISSUE).equal(TransactionConstants.ISSUE_CARD);
        query.field(PURCHASE_DATE).greaterThanOrEq(initDate);
        query.field(PURCHASE_DATE).lessThanOrEq(endDate);
        return query.asList();
    }
    
    public List<Transaction> findSemanticReadedByFileId(ObjectId fileId) throws UnreachableHostException {
        Query<Transaction> query = mDs.find(Transaction.class);
        query.field(FILE_ID).equal(fileId);
        query.field(READ_STATE).equal(TransactionConstants.READ_SEMANTIC);
        query.field(VALIDATION_RESULT).notEqual(TransactionConstants.RESULT_NOT);
        return query.asList();
    }
    
    public List<Transaction> findNotValidatedYet() throws UnreachableHostException {
        Query<Transaction> query = mDs.find(Transaction.class);
        query.field(VALIDATION_RESULT).equal(TransactionConstants.RESULT_NOT);
        query.field(VALIDATION_ISSUE).equal(TransactionConstants.ISSUE_NOT);
        query.field(READ_STATE).equal(TransactionConstants.READ_SYNTAXIS);
        query.order(FILE_NUMBER);
        return query.asList();
    }
    
    public List<Transaction> findApprovedByDateRangeAndFranchiseAndAmountRange(Date initDate, Date endDate,
            ObjectId idFranchise, long initAmount, long endAmount) throws UnreachableHostException {
        Query<Transaction> query = mDs.find(Transaction.class);
        query.field(VALIDATION_RESULT).equal(TransactionConstants.RESULT_APPROVED);
        query.field(VALIDATION_ISSUE).equal(TransactionConstants.ISSUE_NOT);
        query.field(READ_STATE).equal(TransactionConstants.READ_SEMANTIC);
        query.field(PURCHASE_DATE).greaterThanOrEq(initDate);
        query.field(PURCHASE_DATE).lessThanOrEq(endDate);
        query.field(FRANCHISE).equal(idFranchise);
        query.field(PURCHASE_AMOUNT).greaterThanOrEq(initAmount);
        query.field(PURCHASE_AMOUNT).lessThanOrEq(endAmount);
        return query.asList();
    }
    
    public List<Transaction> findApprovedByDateRangeAndFranchiseAndAmountRangeAndBank(Date initDate, Date endDate,
            ObjectId idFranchise, long initAmount, long endAmount, ObjectId idBank) throws UnreachableHostException {
        Query<Transaction> query = mDs.find(Transaction.class);
        query.field(VALIDATION_RESULT).equal(TransactionConstants.RESULT_APPROVED);
        query.field(VALIDATION_ISSUE).equal(TransactionConstants.ISSUE_NOT);
        query.field(READ_STATE).equal(TransactionConstants.READ_SEMANTIC);
        query.field(PURCHASE_DATE).greaterThanOrEq(initDate);
        query.field(PURCHASE_DATE).lessThanOrEq(endDate);
        query.field(FRANCHISE).equal(idFranchise);
        query.field(PURCHASE_AMOUNT).greaterThanOrEq(initAmount);
        query.field(PURCHASE_AMOUNT).lessThanOrEq(endAmount);
        query.field(BANK).equal(idBank);
        return query.asList();
    }
    
    public List<Transaction> findFailedByDateRangeAndFranchiseAndAmountRange(Date initDate, Date endDate,
            ObjectId idFranchise, long initAmount, long endAmount) throws UnreachableHostException {
        Query<Transaction> query = mDs.find(Transaction.class);
        query.field(VALIDATION_RESULT).equal(TransactionConstants.RESULT_FAILED);
        query.field(VALIDATION_ISSUE).notEqual(TransactionConstants.ISSUE_NOT);
        query.field(READ_STATE).equal(TransactionConstants.READ_SEMANTIC);
        query.field(PURCHASE_DATE).greaterThanOrEq(initDate);
        query.field(PURCHASE_DATE).lessThanOrEq(endDate);
        query.field(FRANCHISE).equal(idFranchise);
        query.field(PURCHASE_AMOUNT).greaterThanOrEq(initAmount);
        query.field(PURCHASE_AMOUNT).lessThanOrEq(endAmount);
        return query.asList();
    }
    
    public List<Transaction> findFailedByDateRangeAndFranchiseAndAmountRangeAndBank(Date initDate, Date endDate,
            ObjectId idFranchise, long initAmount, long endAmount, ObjectId idBank) throws UnreachableHostException {
        Query<Transaction> query = mDs.find(Transaction.class);
        query.field(VALIDATION_RESULT).equal(TransactionConstants.RESULT_FAILED);
        query.field(VALIDATION_ISSUE).notEqual(TransactionConstants.ISSUE_NOT);
        query.field(READ_STATE).equal(TransactionConstants.READ_SEMANTIC);
        query.field(PURCHASE_DATE).greaterThanOrEq(initDate);
        query.field(PURCHASE_DATE).lessThanOrEq(endDate);
        query.field(FRANCHISE).equal(idFranchise);
        query.field(PURCHASE_AMOUNT).greaterThanOrEq(initAmount);
        query.field(PURCHASE_AMOUNT).lessThanOrEq(endAmount);
        query.field(BANK).equal(idBank);
        return query.asList();
    }
}